﻿using System;
using System.Reflection;
using MugenInjection.Activators;
using MugenInjection.Attributes;
using MugenInjection.Delegates;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Scope;

namespace MugenInjection.Core
{
    /// <summary>
    /// The default implementation of <see cref="IInjectorSettings"/>.
    /// </summary>
    public sealed class DefaultInjectorSetting : IInjectorSettings
    {
        #region Fields

        private Type _attributeForInject;
        private Type _attributeForAutoLoadModule;
        private bool _isAutoLoadModuleEnabled;
        private bool _useParentToResolve;
        private bool _throwErrorForNullableBinding;
        private Func<IActivator> _defaultActivatorFactory;
        private Func<IConstructorResolver> _defaultConstructorResolverFactory;
        private Func<IScopeLifecycle> _defaultScopeLifecycleFactory;
        private bool _throwErrorIfNotLoadAssembly;
        private bool _isAutoScanAssembly;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="DefaultInjectorSetting" /> class.
        /// </summary>
        public DefaultInjectorSetting()
        {
            _throwErrorForNullableBinding = true;
            _useParentToResolve = true;
            _attributeForInject = typeof(InjectAttribute);
            _defaultConstructorResolverFactory = () => new DefaultConstructorResolver();
            _defaultScopeLifecycleFactory = () => new TransientScopeLifecycle();

#if !NOLOADASSEMBLY
            _attributeForAutoLoadModule = typeof(AutoLoadModuleAttribute);
            _isAutoLoadModuleEnabled = true;
#endif

#if WINDOWS_PHONE7
            _defaultActivatorFactory = () => new ReflectionActivator();
#else

#if NOEXPRESSION
            _defaultActivatorFactory = () => new EmitActivator();
#else
            _defaultActivatorFactory = () => new ExpressionActivator();
#endif

#endif
#if SCANASSEMBLY
            _isAutoScanAssembly = true;
#if !DEBUG
            _throwErrorIfNotLoadAssembly = true;
#endif
#endif
        }

        #endregion

        #region Implementation of IInjectorSettings

        /// <summary>
        /// Gets or sets value, if <c>true</c>Throw error if binding provider return null result; otherwise, <c>false</c>. (Default is <c>true</c>).
        /// </summary>
        public bool ThrowErrorForNullableBinding
        {
            get { return _throwErrorForNullableBinding; }
            set
            {
                if (value == _throwErrorForNullableBinding) return;
                _throwErrorForNullableBinding = value;
                OnSettingChanged("ThrowErrorForNullableBinding", value);
            }
        }

        /// <summary>
        /// Gets or sets value, if <c>false</c> if the binding is not found in the current container, but is found in the parent, activate the binding in the current container; otherwise <c>true</c>. (Default is <c>true</c>).
        /// </summary>
        public bool UseParentToResolve
        {
            get { return _useParentToResolve; }
            set
            {
                if (value == _useParentToResolve) return;
                _useParentToResolve = value;
                OnSettingChanged("UseParentToResolve", value);
            }
        }

#if !NOLOADASSEMBLY

        /// <summary>
        /// Gets or sets value that indicates whether the automatic loading of the module. (Default is <c>true</c>).
        /// </summary>
        public bool IsAutoLoadModuleEnabled
        {
            get { return _isAutoLoadModuleEnabled; }
            set
            {
                if (value == _isAutoLoadModuleEnabled) return;
                _isAutoLoadModuleEnabled = value;
                OnSettingChanged("IsAutoLoadModuleEnabled", value);
            }
        }

        /// <summary>
        /// Gets or sets an attribute that used to automatically load the <see cref="InjectorModule"/>. (Default <see cref="AutoLoadModuleAttribute"/>)
        /// </summary>
        public Type AttributeForAutoLoadModule
        {
            get
            {
                return _attributeForAutoLoadModule;
            }
            set
            {
                if (value == _attributeForAutoLoadModule) return;
                Validate.PropertyNotNull(value, "AttributeForAutoLoadModule");
                Validate.IsAssignable<Attribute>(value);
                _attributeForAutoLoadModule = value;
                OnSettingChanged("AttributeForAutoLoadModule", value);
            }
        }

#endif

#if SCANASSEMBLY

        /// <summary>
        /// Gets or sets value if <c>true</c> throw an error if unable to load the assembly. (Default is <c>true</c>).
        /// </summary>
        public bool ThrowErrorIfNotLoadAssembly
        {
            get { return _throwErrorIfNotLoadAssembly; }
            set
            {
                if (value == _throwErrorIfNotLoadAssembly) return;
                _throwErrorIfNotLoadAssembly = value;
                OnSettingChanged("ThrowErrorIfNotLoadAssembly", value);
            }
        }

        /// <summary>
        /// Gets or sets value that indicates whether the automatic scan of the files, default is <c>true</c>.
        /// </summary>
        public bool IsAutoScanAssembly
        {
            get { return _isAutoScanAssembly; }
            set
            {
                if (value == _isAutoScanAssembly) return;
                _isAutoScanAssembly = value;
                OnSettingChanged("IsAutoScanAssembly", value);
            }
        }
#endif

        /// <summary>
        /// Gets or sets an attribute specifies where to use injection. (Default <see cref="InjectAttribute"/>)
        /// </summary>
        public Type AttributeForInject
        {
            get { return _attributeForInject; }
            set
            {
                if (value == _attributeForInject) return;
                Validate.PropertyNotNull(value, "AttributeForInject");
                Validate.IsAssignable<Attribute>(value);
                _attributeForInject = value;
                OnSettingChanged("AttributeForInject", value);
            }
        }

        /// <summary>
        /// Gets or sets a factory to create <see cref="IActivator"/>.
        /// </summary>
        public Func<IActivator> DefaultActivatorFactory
        {
            get { return _defaultActivatorFactory; }
            set
            {
                if (value == _defaultActivatorFactory) return;
                Validate.PropertyNotNull(value, "DefaultActivatorFactory");
                _defaultActivatorFactory = value;
                OnSettingChanged("DefaultActivatorFactory", value);
            }
        }

        /// <summary>
        /// Gets or sets a factory to create <see cref="IConstructorResolver"/>.
        /// </summary>
        public Func<IConstructorResolver> DefaultConstructorResolverFactory
        {
            get { return _defaultConstructorResolverFactory; }
            set
            {
                if (value == _defaultConstructorResolverFactory) return;
                Validate.PropertyNotNull(value, "DefaultConstructorResolverFactory");
                _defaultConstructorResolverFactory = value;
                OnSettingChanged("DefaultConstructorResolverFactory", value);
            }
        }

        /// <summary>
        /// Gets or sets a factory to create <see cref="ScopeLifecycleBase"/>. Default is <see cref="TransientScopeLifecycle"/>.
        /// </summary>
        public Func<IScopeLifecycle> DefaultScopeLifecycleFactory
        {
            get { return _defaultScopeLifecycleFactory; }
            set
            {
                if (value == _defaultScopeLifecycleFactory) return;
                Validate.PropertyNotNull(value, "DefaultScopeLifecycleFactory");
                _defaultScopeLifecycleFactory = value;
                OnSettingChanged("DefaultScopeLifecycleFactory", value);
            }
        }

        /// <summary>
        /// Occurs when a setting value changes.
        /// </summary>
        public event Action<string, object> SettingChanged;

        /// <summary>
        /// Creates a new object that is a copy of the current instance.
        /// </summary>
        /// <returns>
        /// A new object that is a copy of this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public IInjectorSettings Clone()
        {
            return new DefaultInjectorSetting
                       {
                           _throwErrorForNullableBinding = ThrowErrorForNullableBinding,
                           _useParentToResolve = UseParentToResolve,
                           _attributeForInject = AttributeForInject,
                           _defaultActivatorFactory = DefaultActivatorFactory,
                           _defaultConstructorResolverFactory = DefaultConstructorResolverFactory,
                           _defaultScopeLifecycleFactory = DefaultScopeLifecycleFactory,
#if !NOLOADASSEMBLY
                           _isAutoLoadModuleEnabled = IsAutoLoadModuleEnabled,
                           _attributeForAutoLoadModule = AttributeForAutoLoadModule,
#endif
#if SCANASSEMBLY
                           _throwErrorIfNotLoadAssembly = ThrowErrorIfNotLoadAssembly,
                           _isAutoScanAssembly = IsAutoScanAssembly,
#endif
                       };
        }

        #endregion

        #region Methods

        private void OnSettingChanged(string arg1, object arg2)
        {
            Action<string, object> handler = SettingChanged;
            if (handler != null) handler(arg1, arg2);
        }

        #endregion
    }
}